package com.ly.nethub.netty.tcp

import android.app.Application
import android.content.Context
import com.ly.nethub.config.CODE_TASK_SEND_FAILED
import com.ly.nethub.config.CODE_UNINITIALIZED
import com.ly.nethub.config.ConnectStatus
import com.ly.nethub.config.NetHubConfig
import com.ly.nethub.config.NetHubErrorMsg
import com.ly.nethub.config.NetHubOptions
import com.ly.nethub.interf.ConnectStatusListener
import com.ly.nethub.interf.IDisconnectHandler
import com.ly.nethub.interf.INetHubInterface
import com.ly.nethub.interf.ITaskManager
import com.ly.nethub.interf.OtherMsgCallback
import com.ly.nethub.netty.HeartbeatFactory
import com.ly.nethub.state.AppStateManager
import com.ly.nethub.state.AppStateObserver
import com.ly.nethub.state.INetworkStateChangedObserver
import com.ly.nethub.state.NetworkManager
import com.ly.nethub.task.BaseTask
import com.ly.nethub.utils.CoroutineFactory
import com.ly.nethub.utils.LogUtils
import io.netty.bootstrap.Bootstrap
import io.netty.channel.Channel
import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioSocketChannel
import kotlinx.coroutines.delay
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference


/**
 * 基于Netty实现的TCP客户端
 */
internal class TcpNetHub : INetHubInterface, INetworkStateChangedObserver, IDisconnectHandler,
    AppStateObserver {

    private var mContext: Application? = null

    private var mNetHubOptions: NetHubOptions? = null

    internal val netHubOptions
        get() = mNetHubOptions
            ?: throw IllegalStateException("init() must be called before using netHubOptions")

    private var mHeartbeatFactory: HeartbeatFactory? = null

    internal val heartbeatFactory
        get() = mHeartbeatFactory
            ?: throw IllegalStateException("init() must be called before using heartbeatFactory")

    //连接监听器
    private var mConnectStatusListener: ConnectStatusListener? = null

    //上报的消息回调
    private var mOtherMsgCallback: OtherMsgCallback? = null

    //任务管理
    internal val mTaskManager: ITaskManager = TcpTaskManager(this)

    //心跳管理
    internal val mHeartbeatHub = TcpHeartbeatHandler(this)

    //NettyTcpNetHub是否已关闭
    private val mIsClosed = AtomicBoolean(true)
    val isClosed get() = mIsClosed.get()

    //是否正在重连
    private val mIsReconnecting = AtomicBoolean(false)

    //是否已初始化成功
    private var mInitialized = false

    internal var mBootstrap: Bootstrap? = null
        private set

    //NettyTcpNetHub连接状态
    private val mConnectStatus = AtomicReference<ConnectStatus>(ConnectStatus.unconnected)

    //网络是否可用标识
    internal var mIsNetworkAvailable = false
        private set

    private var mCoroutineFactory: CoroutineFactory? = null


    //使用此字段前必须先调用init方法
    internal val coroutineFactory: CoroutineFactory
        get() = mCoroutineFactory
            ?: throw IllegalStateException("init() must be called before using coroutineServiceFactory")

    internal var mChannel: Channel? = null
        private set

    override fun init(
        context: Context,
        options: NetHubOptions,
        heartbeatFactory: HeartbeatFactory,
        connectStatusListener: ConnectStatusListener?,
        otherMsgCallback: OtherMsgCallback?
    ): Boolean {
        if (mInitialized) {
            LogUtils.d("$TAG init 已初始化过")
            return false
        }
        mCoroutineFactory = CoroutineFactory()
        mNetHubOptions = options
        mConnectStatusListener = connectStatusListener
        mHeartbeatFactory = heartbeatFactory
        NetworkManager.registerObserver(context, this)
        AppStateManager.registerObserver(context, this)
        mInitialized = true
        callbackNetHubConnectStatus(ConnectStatus.unconnected)
        return true
    }


    override suspend fun connect() {
        if (!mInitialized) {
            LogUtils.w("$TAG connect 未初始化")
            return
        }
        reconnect(true)
    }


    override suspend fun reconnect(isFirstConnect: Boolean) {
        if (!isFirstConnect) {
            //非首次连接，代表之前有过重连，
            val interval = mNetHubOptions?.reconnectInterval ?: NetHubConfig.RECONNECT_INTERVAL
            LogUtils.w("$TAG 非首次连接 延迟${interval}ms 再次重连")
            delay(interval)
        }
        if (!mIsClosed.get() && !mIsReconnecting.get()) {
            if (mConnectStatus.get() is ConnectStatus.Connected) {
                LogUtils.w("$TAG 已连接 无需重连")
                return
            }
            LogUtils.w("$TAG 开始准备重连")
            setReconnecting(true)
            closeChannel()
            mCoroutineFactory?.executeBossTask(NettyTcpReconnectTask(this).process())
        }
    }

    override fun disconnect(reconnect: Boolean) {
        mIsReconnecting.set(false)
        mCoroutineFactory?.destroy()
        callbackNetHubConnectStatus(ConnectStatus.unconnected)
        closeChannel()
        if (reconnect) {
            mCoroutineFactory?.enqueueBossTask { reconnect(false) }
        }
    }

    override fun sendTask(task: BaseTask) {
        coroutineFactory.enqueueWorkTask {
            try {
                if (!mInitialized || mChannel == null) {
                    if (!mInitialized) {
                        LogUtils.w("$TAG sendMsg NetHub初始化失败")
                    } else {
                        LogUtils.w("$TAG sendMsg channel未初始化")
                    }
                    task.tryResend(
                        CODE_UNINITIALIZED,
                        NetHubErrorMsg[CODE_UNINITIALIZED]
                    )
                    return@enqueueWorkTask
                }
                val status = mConnectStatus.get()
                if (status !is ConnectStatus.Connected) {
                    LogUtils.w("$TAG sendMsg 不是处于已连接状态 $status")
                    task.tryResend(status.code, status.msg)
                    return@enqueueWorkTask
                }
                mTaskManager.tryExecute(task)
            } catch (e: Exception) {
                task.tryResend(
                    CODE_TASK_SEND_FAILED,
                    NetHubErrorMsg[CODE_TASK_SEND_FAILED]
                )
            }
        }
    }

    override fun onNetworkAvailable() {
        mIsNetworkAvailable = true
        LogUtils.d("$TAG 网络可用，启动NetHub")
        mIsClosed.set(false)
        //网络连接时，重置NetHub
        mCoroutineFactory?.enqueueBossTask {
            reconnect(true)
        }
    }

    override fun onNetworkUnavailable() {
        mIsNetworkAvailable = false
        LogUtils.d("$TAG 网络不可用，关闭NetHub")
        mIsClosed.set(true)
        mIsReconnecting.set(false)
        //网络断开，关闭重连任务协程
        mCoroutineFactory?.destroyBossScope()
        callbackNetHubConnectStatus(ConnectStatus.networkUnavailable)
        //关闭channel
        closeChannel()
        //关闭bootstrap
        closeBootstrap()
    }

    override fun onBackground() {
        mHeartbeatHub.onBackground()
    }

    override fun onForeground() {
        mHeartbeatHub.onForeground()
    }

    override fun release() {
        closeChannel()
        closeBootstrap()
        mInitialized = false
        if (mCoroutineFactory != null) {
            mCoroutineFactory?.destroy()
            mCoroutineFactory = null
        }
        mContext?.let {
            NetworkManager.unregisterObserver(it, this)
            AppStateManager.unregisterObserver(it, this)
        }
        mTaskManager.destroy()
        mContext = null
    }

    override fun handleUnexpectedDisconnection(cause: Throwable?) {
        closeChannel()
        //回调连接失败状态
        callbackNetHubConnectStatus(ConnectStatus.failed)
        coroutineFactory.enqueueBossTask {
            //触发重连
            reconnect(false)
        }
    }

    /**
     * 初始化bootstrap
     */
    fun initBootstrap() {
        closeBootstrap()
        val loopGroup = NioEventLoopGroup(4)
        mBootstrap = Bootstrap().also {
            it.group(loopGroup)
                .channel(NioSocketChannel::class.java)
                //设置该选项后，如果两小时内有没有数据通讯，TCP会自动发送一个活动探测数据报文
                .option(ChannelOption.SO_KEEPALIVE, true)
                //设置禁用nagle算法，如果要求高实时性，有数据发送时就马上发送，就将该选项设置为true关闭Nagle算法；
                //如果要减少发送次数减少网络交互，就设置为false等累积一定大小后再发送。默认为false
                .option(ChannelOption.TCP_NODELAY, true)
                //设置TCP发送缓冲区大小 byte
                .option(ChannelOption.SO_SNDBUF, 32 * 1024)
                //设置TCP接收缓冲区大小 byte
                .option(ChannelOption.SO_RCVBUF, 32 * 1024)
                //设置连接超时时长
                .option(
                    ChannelOption.CONNECT_TIMEOUT_MILLIS,
                    (mNetHubOptions?.connectTimeout ?: NetHubConfig.CONNECT_TIMEOUT).toInt()
                )
                //设置初始化ChannelHandler
                .handler(NettyTcpChannelInitializerHandler(this))
        }
        LogUtils.d("$TAG initBootstrap mBootstrap $mBootstrap")
    }

    private suspend fun BaseTask.tryResend(code: Int, msg: String) {
        val listener = callback
        if (!isAllowResend) {
            listener?.onTaskError(code, msg)
        } else {
            increaseRetryCount()
            mTaskManager.tryExecute(this)
        }
    }

    /**
     * 关闭channel
     */
    private fun closeChannel() {
        try {
            mChannel?.removeHandler(NettyTcpReadHandler::class.java.simpleName)
            try {
                mChannel?.close()?.await()
            } catch (e: Exception) {
                e.printStackTrace()
            }

            try {
                mChannel?.eventLoop()?.shutdownGracefully()?.addListener {
                    LogUtils.d("$TAG closeChannel 关闭通道结果 ${it.isSuccess}")
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }

        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            mChannel = null
        }
    }

    private fun Channel.removeHandler(name: String) {
        try {
            val pipeline = pipeline()
            if (pipeline.get(name) != null) {
                pipeline.remove(name)
            }
        } catch (e: Exception) {
            e.printStackTrace()
            LogUtils.d("$TAG removeHandler 移除handler失败:$name")
        }
    }

    /**
     * 关闭bootstrap
     */
    private fun closeBootstrap() {
        LogUtils.d("$TAG closeBootstrap start")
        try {
            val group = mBootstrap?.config()?.group()
            group?.shutdownGracefully()?.addListener {
                LogUtils.d("$TAG closeBootstrap group shutdown ${it.isSuccess}")
            }
        } catch (e: Exception) {
            e.printStackTrace()
            LogUtils.d("$TAG closeBootstrap error $e")
        } finally {
            mBootstrap = null
        }
    }

    internal fun callbackNetHubConnectStatus(status: ConnectStatus) {
        if (mConnectStatus.get() == status) {
            LogUtils.d("$TAG 回调连接状态 与上一次状态相同，无需刷新")
            return
        }
        mConnectStatus.set(status)
        mTaskManager.onConnectStatusChanged(status)
        LogUtils.d("$TAG 回调连接状态:$status")
        if (status is ConnectStatus.Connected) {
            mHeartbeatHub.start()
        } else {
            mHeartbeatHub.stop()
        }
        mConnectStatusListener?.apply {
            when (status) {
                is ConnectStatus.ConnectFailed -> onConnectFailed(status.code, status.msg)
                is ConnectStatus.Connected -> onConnected()
                is ConnectStatus.Connecting -> onConnecting()
                is ConnectStatus.Unconnected -> onUnconnected()
            }
        }

    }

    internal fun setChannel(channel: Channel) {
        mChannel = channel
    }

    internal fun setReconnecting(isReconnecting: Boolean) {
        mIsReconnecting.set(isReconnecting)
    }

    /**
     * 不要的消息
     */
    internal fun onDiscardedMsg(msg: Any?) {
        LogUtils.d("$TAG 不能处理的消息 $msg")
        mOtherMsgCallback?.invoke(msg)
    }

    companion object {
        private const val TAG = "NettyTcpNetHub"
    }

}